/**
 * Copyright (c) 2009-2010 Thales Corporate Services S.A.S.
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-v2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 * Thales Corporate Services S.A.S - initial API and implementation
 */
package org.eclipse.egf.model.fcore.helper;

import java.util.Collection;
import java.util.Iterator;

import org.eclipse.egf.model.fcore.Contract;
import org.eclipse.egf.model.fcore.ContractMode;
import org.eclipse.egf.model.fcore.FactoryComponentContract;
import org.eclipse.egf.model.fcore.Invocation;
import org.eclipse.egf.model.fcore.InvocationContract;
import org.eclipse.egf.model.fcore.OrchestrationParameter;
import org.eclipse.egf.model.types.Type;
import org.eclipse.emf.common.util.UniqueEList;

/**
 * @author Xavier Maysonnave
 * 
 */
public class InvocationContractHelper {

    private InvocationContractHelper() {
        // Prevent Instantiation
    }

    public static Collection<Contract> getAvailableFactoryComponentContract(InvocationContract invocationContract) {
        Collection<Contract> result = new UniqueEList<Contract>();
        // Nothing to retrieve
        if (invocationContract.getFactoryComponent() == null || invocationContract.getInvokedContract() == null || invocationContract.getInvokedContract().getType() == null) {
            return result;
        }
        // If an OrchestrationParameter is already assigned, InvocationContract in In mode are not assignable
        if (invocationContract.getOrchestrationParameter() != null && invocationContract.getInvokedMode() == ContractMode.IN) {
            return result;
        }
        // Retrieve all the typed contracts if available
        if (invocationContract.getInvokedMode() == ContractMode.IN) {
            result.addAll(invocationContract.getFactoryComponent().getContracts(invocationContract.getInvokedContract().getType(), ContractMode.IN));
        } else {
            // In or In_Out Contract should have only one assigned InvocationContract.
            for (Contract contract : invocationContract.getFactoryComponent().getContracts(invocationContract.getInvokedContract().getType(), invocationContract.getInvokedMode())) {
                if (((FactoryComponentContract) contract).getInvocationContracts().size() == 0) {
                    result.add(contract);
                }
            }
        }
        // If an OrchestrationParameter or a SourceInvocationContract is already assigned, InvocationContract in In_Out mode are only assignable to an Out Mode Contract
        if ((invocationContract.getOrchestrationParameter() != null || invocationContract.getSourceInvocationContract() != null) && invocationContract.getInvokedMode() == ContractMode.IN_OUT) {
            for (Iterator<Contract> it = result.iterator(); it.hasNext();) {
                Contract contract = it.next();
                if (contract != null && contract.getMode() != ContractMode.OUT) {
                    it.remove();
                }
            }
        }
        return result;
    }

    public static Collection<OrchestrationParameter> getAvailableOrchestrationParameter(InvocationContract invocationContract) {
        Collection<OrchestrationParameter> result = new UniqueEList<OrchestrationParameter>();
        // Nothing to retrieve
        if (invocationContract.getFactoryComponent() == null || invocationContract.getFactoryComponent().getOrchestration() == null || invocationContract.getInvokedContract() == null || invocationContract.getInvokedContract().getType() == null) {
            return result;
        }
        // InvocationContract in Out mode are not assignable
        if (invocationContract.getInvokedMode() == ContractMode.OUT) {
            return result;
        }
        // InvocationContract already assigned to a FactoryComponentContract should be in In_Out mode
        if (invocationContract.getFactoryComponentContract() != null && invocationContract.getInvokedMode() != ContractMode.IN_OUT) {
            return result;
        }
        // InvocationContract already assigned to a SourceInvocationContract are not assignable
        if (invocationContract.getSourceInvocationContract() != null) {
            return result;
        }
        // Retrieve all compatible typed OrchestrationParameter
        result.addAll(invocationContract.getFactoryComponent().getOrchestration().getOrchestrationParameters(invocationContract.getInvokedContract().getType()));
        // Return
        return result;
    }

    public static Collection<Contract> getAvailableInvokedContract(InvocationContract invocationContract) {
        Collection<Contract> result = new UniqueEList<Contract>();
        // Nothing to retrieve
        if (invocationContract.getInvocation() == null || invocationContract.getInvocation().getInvokedActivity() == null) {
            return result;
        }
        // Type filtering
        if (invocationContract.getType() != null) {
            if (invocationContract.getFactoryComponentContract() != null) {
                result.addAll(invocationContract.getInvocation().getInvokedActivity().getContracts(invocationContract.getType(), invocationContract.getFactoryComponentContract().getMode()));
            } else {
                result.addAll(invocationContract.getInvocation().getInvokedActivity().getContracts(invocationContract.getType()));
            }
            // Filter all assigned contracts if necessary
            if (result.size() > 0) {
                for (Contract innerContract : invocationContract.getInvocation().getInvokedContracts(invocationContract.getType())) {
                    result.remove(innerContract);
                }
            }
        } else {
            if (invocationContract.getFactoryComponentContract() != null) {
                result.addAll(invocationContract.getInvocation().getInvokedActivity().getContracts(invocationContract.getFactoryComponentContract().getMode()));
            } else {
                result.addAll(invocationContract.getInvocation().getInvokedActivity().getContracts());
            }
            // Filter all assigned contracts if necessary
            if (result.size() > 0) {
                for (Contract innerContract : invocationContract.getInvocation().getInvokedContracts()) {
                    result.remove(innerContract);
                }
            }
        }
        return result;
    }

    public static Collection<InvocationContract> getAvailableSourceInvocationContract(InvocationContract invocationContract) {
        Collection<InvocationContract> result = new UniqueEList<InvocationContract>();
        // Nothing to retrieve
        if (invocationContract.getInvocation() == null || invocationContract.getFactoryComponent() == null || invocationContract.getFactoryComponent().getOrchestration() == null) {
            return result;
        }
        // To get an assignable source InvocationContract the current invocationContract should have an In or an In_Out Contract Mode.
        if (invocationContract.getInvokedContract() == null || invocationContract.getInvokedMode() == ContractMode.OUT || invocationContract.getInvokedContract().getType() == null) {
            return result;
        }
        // Store the current type
        Type type = invocationContract.getType();
        if (type == null) {
            type = invocationContract.getInvokedContract().getType();
        }
        // InvocationContract already assigned to an OrchestrationParameter are not assignable
        if (invocationContract.getOrchestrationParameter() != null) {
            return result;
        }
        // Assigned FactoryComponentContract should be in Out mode.
        if (invocationContract.getFactoryComponentContract() != null && invocationContract.getFactoryComponentContract().getMode() != ContractMode.OUT) {
            return result;
        }
        // Source should be an In_Out or an Out mode contract
        for (Invocation invocation : invocationContract.getFactoryComponent().getOrchestration().getInvocations()) {
            // First we check our current Invocation, all retrieved Invocation should be above
            if (invocation == invocationContract.getInvocation()) {
                break;
            }
            // InvocationContract analysis
            for (InvocationContract innerInvocationContract : invocation.getInvocationContracts()) {
                // InvokedContract and its according type should be assigned
                if (innerInvocationContract.getInvokedContract() == null || innerInvocationContract.getInvokedContract().getType() == null) {
                    continue;
                }
                // Then we check In_Out or Out mode contract
                if (innerInvocationContract.getInvokedMode() != ContractMode.IN_OUT && innerInvocationContract.getInvokedMode() != ContractMode.OUT) {
                    continue;
                }
                // Then we check if this innerInvocationContract is already assigned
                if (innerInvocationContract.getSourceInvocationContract() != null) {
                    continue;
                }
                // Finally we check if their respective types are compatible
                Type innerType = innerInvocationContract.getType();
                if (innerType == null) {
                    innerType = innerInvocationContract.getInvokedContract().getType();
                }
                if (type.isCompatible(innerType)) {
                    result.add(innerInvocationContract);
                }
            }
        }
        return result;
    }

    public static Collection<InvocationContract> getAvailableTargetInvocationContract(InvocationContract invocationContract) {
        Collection<InvocationContract> result = new UniqueEList<InvocationContract>();
        // Nothing to retrieve
        if (invocationContract.getInvocation() == null || invocationContract.getFactoryComponent() == null || invocationContract.getFactoryComponent().getOrchestration() == null) {
            return result;
        }
        // To assign a target InvocationContract the current invocationContract should have an Out or an In_Out Contract Mode.
        if (invocationContract.getInvokedContract() == null || invocationContract.getInvokedMode() == ContractMode.IN || invocationContract.getInvokedContract().getType() == null) {
            return result;
        }
        // Store the current type
        Type type = invocationContract.getType();
        if (type == null) {
            type = invocationContract.getInvokedContract().getType();
        }
        // Target should be an In_Out or an In mode contract
        boolean analyse = false;
        for (Invocation invocation : invocationContract.getFactoryComponent().getOrchestration().getInvocations()) {
            // First we check our current Invocation, all retrieved InvocationContract should be below, ignore current
            if (invocation == invocationContract.getInvocation()) {
                analyse = true;
                continue;
            }
            if (analyse == false) {
                continue;
            }
            for (InvocationContract innerInvocationContract : invocation.getInvocationContracts()) {
                // InvokedContract and its according type should be assigned
                if (innerInvocationContract.getInvokedContract() == null || innerInvocationContract.getInvokedContract().getType() == null) {
                    continue;
                }
                // InvocationContract already assigned to an OrchestrationParameter are not assignable
                if (innerInvocationContract.getOrchestrationParameter() != null) {
                    continue;
                }
                // Assigned FactoryComponentContract should be in Out mode.
                if (innerInvocationContract.getFactoryComponentContract() != null && innerInvocationContract.getFactoryComponentContract().getMode() != ContractMode.OUT) {
                    continue;
                }
                // Then we check In_Out or Out mode contract
                if (innerInvocationContract.getInvokedMode() != ContractMode.IN_OUT && innerInvocationContract.getInvokedMode() != ContractMode.IN) {
                    continue;
                }
                // Then we check if this innerInvocationContract is already assigned
                if (innerInvocationContract.getSourceInvocationContract() != null) {
                    continue;
                }
                // Finally we check if their respective types are compatible
                Type innerType = innerInvocationContract.getType();
                if (innerType == null) {
                    innerType = innerInvocationContract.getInvokedContract().getType();
                }
                if (innerType.isCompatible(type)) {
                    result.add(innerInvocationContract);
                }
            }
        }
        return result;
    }

}
